package com.bcfn.bshop.common.utils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.bcfn.bshop.common.annotation.DtoField;
import com.bcfn.bshop.common.dto.BaseVersionDto;
import com.bcfn.bshop.common.exception.ErrorInfoException;
import com.bcfn.bshop.common.model.BaseVersionModel;

/**
 * 
 * @author LiangGuoNing.
 * 
 * 2019年1月18日 下午6:04:56.
 *
 * 修改记录：
 *
 */
public class DtoBuilder {
	
	/**
	 * 合并Dto和Model,检查版本号,防止并发更新.
	 * @param dto
	 * @param model
	 * @throws ErrorInfoException
	 */
	public static void mergeToModel(Object dto, Object model) throws ErrorInfoException {
		//to check the version.
		checkVersion(dto, model);
		
		Class<?> dtoClass = dto.getClass();
		Class<?> modelClass = model.getClass();
		
		Method[] dtoGetMethods = ReflectUtil.getAllGetMethod(dtoClass);
		Map<String, Method> modelSetMethods = ReflectUtil.getAllSetMethodsToMap(modelClass);
		
		int size = dtoGetMethods.length;
		try {
			for(int i = 0; i < size; i++) {
				DtoField dtoFieldAnnt = dtoGetMethods[i].getAnnotation(DtoField.class);
				if (dtoFieldAnnt == null) {
					String mergeToMethodName = dtoGetMethods[i].getName().replaceFirst("g", "s");
					Method modelSetMethod = modelSetMethods.get(mergeToMethodName);
					if (modelSetMethod != null) {
						Object value = dtoGetMethods[i].invoke(dto);
						if(value != null) {
							modelSetMethod.invoke(model, value);
						}
					}
				} else {
					if (dtoFieldAnnt.ignoreAtMergeModel() == false) {
						String mergeToFieldName = dtoFieldAnnt.mergeToField();
						if("".equalsIgnoreCase(mergeToFieldName)) {
							mergeToFieldName = dtoGetMethods[i].getName().replaceFirst("g", "s");
						}
						Method modelSetMethod = modelSetMethods.get(mergeToFieldName);
						if (modelSetMethod != null) {
							Object value = dtoGetMethods[i].invoke(dto);
							if(value != null || dtoFieldAnnt.mergeNullValue() == true){
								modelSetMethod.invoke(model, value);
							}
						}
					}
				}
			}
		} catch (Exception e) {
			throw new ErrorInfoException(e);
		}
	}
	
	/**
	 * 
	 * @param dto
	 * @param modelClass
	 * @return
	 * @throws ErrorInfoException
	 */
	public static <T> T buildModel(Object dto, Class<T> modelClass) throws ErrorInfoException {
		Class<?> dtoClass = dto.getClass();
		T model = null;
		
		Method[] dtoGetMethods = ReflectUtil.getAllGetMethod(dtoClass);
		Map<String, Method> modelSetMethods = ReflectUtil.getAllSetMethodsToMap(modelClass);
		
		int size = dtoGetMethods.length;
		try {
			model = modelClass.newInstance();
			for(int i = 0; i < size; i++) {
				DtoField dtoFieldAnnt = dtoGetMethods[i].getAnnotation(DtoField.class);
				if (dtoFieldAnnt == null) {
					String mergeToMethodName = dtoGetMethods[i].getName().replaceFirst("g", "s");
					Method modelSetMethod = modelSetMethods.get(mergeToMethodName);
					if (modelSetMethod != null) {
						Object value = dtoGetMethods[i].invoke(dto);
						if(value != null) {
							modelSetMethod.invoke(model, value);
						}
					}
				} else {
					if (dtoFieldAnnt.ignoreAtMergeModel() == false) {
						String mergeToFieldName = dtoFieldAnnt.mergeToField();
						if("".equalsIgnoreCase(mergeToFieldName)) {
							mergeToFieldName = dtoGetMethods[i].getName().replaceFirst("g", "s");
						}
						Method modelSetMethod = modelSetMethods.get(mergeToFieldName);
						if (modelSetMethod != null) {
							Object value = dtoGetMethods[i].invoke(dto);
							if(value != null || dtoFieldAnnt.mergeNullValue() == true){
								modelSetMethod.invoke(model, value);
							}
						}
					}
				}
			}
		} catch (Exception e) {
			throw new ErrorInfoException(e);
		}
		return model;
	}
	
	/**
	 * 
	 * @param model
	 * @param dtoClass
	 * @return
	 * @throws ErrorInfoException
	 */
	@SuppressWarnings("unchecked")
	public static <T> T buildDto(Object model, Class<T> dtoClass) throws ErrorInfoException {
		if(model == null) {
			return null;
		}
		
		Class<?> modelClazz = model.getClass();
		if (modelClazz.equals(java.util.ArrayList.class) ) {
			try {
				Field elementDataField = modelClazz.getDeclaredField("elementData");
				elementDataField.setAccessible(true);
				Object modelElementData =  elementDataField.get(model);
				if (modelElementData != null) {
					Object[] modelElementValues = (Object[])modelElementData;
					Class<T> elementClass = (Class<T>)((ParameterizedType)dtoClass.getGenericSuperclass()).getActualTypeArguments()[0];
					List<T> list = new ArrayList<T>();
					
					Field sizeField = modelClazz.getDeclaredField("size");
					sizeField.setAccessible(true);
					int size = (Integer)sizeField.get(model);
					for(int i = 0; i < size; i++) {
						T element = buildSimpleDto(modelElementValues[i], elementClass);
						list.add(element);
					}
					return (T)list; 
				} else {
					return null;
				}
			} catch (Exception e) {
				throw new ErrorInfoException(e);
			}
		} else {
			return buildSimpleDto(model, dtoClass);
		}
	}
	
	/**
	 * 
	 * @param model
	 * @param dtoClass
	 * @return
	 * @throws ErrorInfoException
	 */
	private static <T> T buildSimpleDto(Object model, Class<T> dtoClass) throws ErrorInfoException {
		if(model == null) {
			return null;
		}
	
		Method[] dtoSetMethods = ReflectUtil.getAllSetMethod(dtoClass);
		int size = dtoSetMethods.length;
		Class<?> modelClass = model.getClass();
		Map<String, Method> modelGetMethods = ReflectUtil.getAllGetMethodsToMap(modelClass);
		try {
			T dto = dtoClass.newInstance();
			for(int i = 0; i < size; i++) {
				DtoField dtoFieldAnnt = dtoSetMethods[i].getAnnotation(DtoField.class);
				if (dtoFieldAnnt == null) {
					String buildFromFieldName = dtoSetMethods[i].getName().replaceFirst("s", "g");
					Method modelGetMethod = modelGetMethods.get(buildFromFieldName);
					if(modelGetMethod != null) {
						Object value = modelGetMethod.invoke(model);
						if(value != null) {
							dtoSetMethods[i].invoke(dto, value);
						}
					}
				} else {
					if (dtoFieldAnnt.ignoreAtBuildDto() == false) {
						String buildFromFieldName = dtoFieldAnnt.buildFromField();
						if("".equalsIgnoreCase(buildFromFieldName)) {
							buildFromFieldName = dtoSetMethods[i].getName();
						}
						
						Method modelGetMethod = modelGetMethods.get("get" + StringUtils.capitalize(buildFromFieldName));
						if(modelGetMethod != null) {
							Object value = modelGetMethod.invoke(model);
							if (value != null) {
								dtoSetMethods[i].invoke(dto, value);
							}
						}
					}
				}
			}
			return dto;
		} catch (Exception e) {
			throw new ErrorInfoException(e);
		}
	} 
	
	/**
	 * 检查版本号.
	 * @param dto
	 * @param model
	 * @throws ErrorInfoException
	 */
	public static void checkVersion(Object dto, Object model) throws ErrorInfoException {
		Class<?> baseDtoClass = BaseVersionDto.class;
    	try {
	    	Field dtoVersionField = baseDtoClass.getDeclaredField("versionId");
	    	dtoVersionField.setAccessible(true);
	    	Object dtoVersionId = dtoVersionField.get(dto);
	    	
	    	Class<?> baseModelClass = BaseVersionModel.class;
	    	Field modelVersionField = baseModelClass.getDeclaredField("versionId");
	    	modelVersionField.setAccessible(true);
	    	Object modelVersionId = modelVersionField.get(model);
	    	
	    	if(modelVersionId != null && modelVersionId.equals(dtoVersionId) == false) {
	    		throw new ErrorInfoException("版本更新错误，版本号已被其他程序更新.");
	    	}
    	} catch (Exception e) {
    		throw new ErrorInfoException(e);
    	}
	}
}

